﻿using Lyon.MemoryShares;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting.Messaging;

namespace Lyon.EventMethods
{
    public class EventMethodService
    {
        private Dictionary<string, object> serviceList = new Dictionary<string, object>();
        private Dictionary<string, object> proxyObjectList = new Dictionary<string, object>();
        private Queue<object> SendList = new Queue<object>();
        private Queue<object> ReceivedList = new Queue<object>();

        private EventMethodAttribute GetMethodAttribute(Type serviceType, MethodInfo methodInfo)
        {
            EventMethodAttribute attribute = EventMethodAttribute.GetCustomAttribute(methodInfo, typeof(EventMethodAttribute)) as EventMethodAttribute;
            if (attribute != null)
                return attribute;
            attribute = EventMethodAttribute.GetCustomAttribute(serviceType, typeof(EventMethodAttribute)) as EventMethodAttribute;
            if (attribute != null)
                return attribute;
            return EventMethodAttribute.Default;
        }

        private object GetService(string url,Type serviceType)
        {
            object serviceObject;
            if(serviceList.TryGetValue(url, out serviceObject))
            {
                return serviceObject;
            }
            return null;
        }
        private object GetProxy(string url, Type serviceType)
        {
            object proxyObject = null;
            if (!proxyObjectList.TryGetValue(url, out proxyObject))
            {
                MethodProxy proxy = new MethodProxy(this, serviceType, url);
                proxyObject = proxy.GetTransparentProxy();
                proxyObjectList.Add(url, proxyObject);
            }
            return proxyObject;
        }
        private string GetServiceUrl(object service, Type serviceType)
        {
            if (serviceType.IsInterface)
            {
                if (serviceList.ContainsValue(service))
                {
                    return serviceList.Where(item => item.Value == service).First().Key;
                }
                else
                {
                    string url = Guid.NewGuid().ToString() + "_" + service.GetType().AssemblyQualifiedName;
                    serviceList.Add(url, service);
                    return url;
                }
            }
            else
            {
                return null;
            }
        }
        private string GetProxyUrl(object service,Type serviceType)
        {
            if (!System.Runtime.Remoting.RemotingServices.IsTransparentProxy(service))
            {
                return null;
            }
            MethodProxy proxy = System.Runtime.Remoting.RemotingServices.GetRealProxy(service) as MethodProxy;
            if (proxy == null)
                return null;
            return proxy.url;

            //if (proxyObjectList.ContainsValue(service))
            //{
            //    return proxyObjectList.Where(item => item.Value == service).First().Key;
            //}
            //else
            //{
            //    return null;
            //}
        }
        private EventMethodHandle FindHandle(object value, Type serviceType)
        {
            string proxyUrl = this.GetProxyUrl(value, serviceType);
            if (!string.IsNullOrEmpty(proxyUrl))
                return new EventMethodHandle() { Value = proxyUrl, IsProxy = true };
            string argUrl = GetServiceUrl(value, serviceType);
            if (!string.IsNullOrEmpty(argUrl))
                return new EventMethodHandle() { Value = argUrl, IsProxy = false };
            return null;
        }
        private object FindObject(EventMethodHandle url, Type serviceType)
        {
            if (url.IsProxy)
                return GetService(url.Value,serviceType);
            else
                return GetProxy(url.Value, serviceType);
        }

        private EventMethodReturn ServiceInvoke(EventMethodCall call)
        {
            object serviceObject = GetService(call.Url, call.UrlType);

            ParameterInfo[] pInfos = call.Method.GetParameters();
            object[] ps = new object[pInfos.Length];
            for (int i = 0; i < pInfos.Length; i++)
            {
                if (call.Args[i] is EventMethodHandle)
                    ps[i] = FindObject((EventMethodHandle)call.Args[i], pInfos[i].ParameterType);
                else
                    ps[i] = call.Args[i];
            }

            object returnValue = null;
            try
            {
                returnValue = call.Method.Invoke(serviceObject, ps);
            }
            catch (TargetInvocationException ex)
            {
                return new EventMethodReturn()
                {
                    CallID = call.ID,
                    Exception = ex.InnerException,
                };
            }
            EventMethodHandle methodUrl = this.FindHandle(returnValue, call.Method.ReturnType);
            return new EventMethodReturn()
            {
                CallID = call.ID,
                ReturnValue = methodUrl == null ? returnValue : methodUrl,
            };
        }
        private object ProxyInvoke(string url, Type serviceType, MethodInfo methodInfo, object[] args)
        {
            EventMethodAttribute attribute = GetMethodAttribute(serviceType, methodInfo);

            ParameterInfo[] pInfos = methodInfo.GetParameters();
            object[] ps = new object[pInfos.Length];
            for (int i = 0; i < pInfos.Length; i++)
            {
                EventMethodHandle temp = FindHandle(args[i], pInfos[i].ParameterType);
                if (temp == null)
                    ps[i] = args[i];
                else
                    ps[i] = temp;
            }

            if (attribute.IgnoreReturn && methodInfo.ReturnType == typeof(void))
            {
                EventMethodCall callMessage = new EventMethodCall()
                {
                    Url = url,
                    UrlType = serviceType,
                    Method = methodInfo,
                    Args = ps,
                    IgnoreReturn = true,
                };
                SendList.Enqueue(callMessage);
                return null;
            }
            else
            {
                EventMethodCall callMessage = new EventMethodCall()
                {
                    ID = Guid.NewGuid().ToString(),
                    Url = url,
                    UrlType = serviceType,
                    Method = methodInfo,
                    Args = ps,
                    IgnoreReturn = false,
                };
                SendList.Enqueue(callMessage);
                MethodWait waiter = new MethodWait(this, callMessage);
                EventMethodReturn returnValue = waiter.Start(attribute.Timeout);
                if (returnValue == null)
                    throw new TimeoutException(string.Format("调用类型“{0}”的“{1}”超过{2}毫秒.", callMessage.Url, callMessage.Method.ToString(), attribute.Timeout));
                if (returnValue.Exception != null)
                    throw returnValue.Exception;
                else if (returnValue.ReturnValue is EventMethodHandle)
                    return FindObject((EventMethodHandle)returnValue.ReturnValue, methodInfo.ReturnType);
                else
                    return returnValue.ReturnValue;
            }
        }

        private void HandleCall(EventMethodCall call)
        {
            if (call.IgnoreReturn)
            {
                ServiceInvoke(call);
            }
            else
            {
                SendList.Enqueue(ServiceInvoke(call));
            }
        }

        private Action<EventMethodReturn> HandleReturn;

        public void Marshal(object service, string url, Type serviceType)
        {
            if (serviceType.IsInterface)
            {
                serviceList.Remove(url);
                serviceList.Add(url, service);
            }
            else
            {
                throw new NotSupportedException();
            }
        }
        public object Connect(Type serviceType, string url)
        {
            return GetProxy(url, serviceType);
        }

        public void DoEvent()
        {
            object receivedObject;
            while (ReceivedList.Count > 0)
            {
                lock (ReceivedList)
                {
                    receivedObject = ReceivedList.Dequeue();
                }
                if (receivedObject is EventMethodCall)
                {
                    HandleCall((EventMethodCall)receivedObject);
                }
                else if (receivedObject is EventMethodReturn)
                {
                    HandleReturn?.Invoke((EventMethodReturn)receivedObject);
                }
            }
        }
        public object GetEvent()
        {
            if (SendList.Count == 0)
            {
                return null;
            }
            else
            {
                lock (SendList)
                {
                    return SendList.Dequeue();
                }
            }
        }
        public void SetEvent(object value)
        {
            lock (ReceivedList)
            {
                ReceivedList.Enqueue(value);
            }
        }

        private class MethodProxy : System.Runtime.Remoting.Proxies.RealProxy
        {
            public string url;
            private Type handleType;
            private EventMethodService owner;

            public MethodProxy(EventMethodService owner, Type handleType, string url)
                : base(handleType)
            {
                // TODO: Complete member initialization
                this.owner = owner;
                this.handleType = handleType;
                this.url = url;
            }

            public override IMessage Invoke(IMessage msg)
            {
                IMethodCallMessage methodCall = (IMethodCallMessage)msg;
                try
                {
                    object returnValue = owner.ProxyInvoke(url, handleType, (MethodInfo)methodCall.MethodBase, methodCall.Args);
                    return new ReturnMessage(returnValue, null, 0, null, methodCall);
                }
                catch (Exception ex)
                {
                    return new ReturnMessage(ex, methodCall);
                }
            }
        }
        private class MethodWait
        {
            EventMethodService _Owner;
            EventMethodCall Call;
            EventMethodReturn Back;
            bool wait;

            public MethodWait(EventMethodService owner, EventMethodCall call)
            {
                _Owner = owner;
                Call = call;

            }
            public EventMethodReturn Start(int totalMilliseconds)
            {
                _Owner.HandleReturn += ReturnReceived;
                wait = true;
                var watch = System.Diagnostics.Stopwatch.StartNew();
                while (wait && watch.ElapsedMilliseconds < totalMilliseconds)
                {
                    _Owner.DoEvent();
                }
                watch.Stop();
                _Owner.HandleReturn -= ReturnReceived;
                return Back;
            }

            private void ReturnReceived(EventMethodReturn value)
            {
                if (value.CallID == Call.ID)
                {
                    Back = value;
                    wait = false;
                }
            }
        }
    }

    public class EventMethodDispatcher
    {
        System.Threading.Thread _ReceiveThread;
        System.Threading.Thread _SendThread;
        bool canRun;
        MemoryServer server;
        MemoryClient client;

        EventMethodService _EventList;

        public EventMethodDispatcher(EventMethodService value)
        {
            _EventList = value;
        }

        public void Start(string sourName, string destName)
        {
            server = new MemoryServer(sourName);
            client = new MemoryClient(destName);
            canRun = true;
            _ReceiveThread = new System.Threading.Thread(ReceiveMessage);
            _ReceiveThread.Start();
            _SendThread = new System.Threading.Thread(SendMessage);
            _SendThread.Start();
        }

        public void Stop()
        {
            _SendThread.Abort();
            _ReceiveThread.Abort();
            canRun = false;
            client.Dispose();
            server.Dispose();
        }

        private void ReceiveMessage()
        {
            server.Accept();
            object message = null;
            while (canRun)
            {
                try
                {
                    message = server.Receive();
                }
                catch
                {
                    message = null;
                    System.Threading.Thread.Sleep(500);
                }
                if (message == null)
                {
                    System.Threading.Thread.Sleep(10);
                }
                else
                {
                    _EventList.SetEvent(message);
                }
            }
        }
        private void SendMessage()
        {
            client.Connect();
            object message = null;
            while (canRun)
            {
                message = _EventList.GetEvent();
                if (message == null)
                {
                    System.Threading.Thread.Sleep(10);
                }
                else
                {
                    client.Send(message);
                }
            }
        }
    }
}